<!DOCTYPE html>
<!--
Copyright 2017 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/value/diagnostics/diagnostic.html">

<script>
'use strict';

tr.exportTo('tr.v.d', function() {
  /**
   * Stringify Arrays or dictionaries. Sorts dictionaries keys. Non-recursive.
   *
   * @param {!Object} obj
   * @return {string}
   */
  function stableStringify(obj) {
    let replacer;
    if (!(obj instanceof Array) && obj !== null) {
      replacer = Object.keys(obj).sort();
    }
    return JSON.stringify(obj, replacer);
  }

  /**
   * @typedef {(null|number|string|boolean|Array.<!PlainOldData>|!Object)}
   * PlainOldData
   */

  class GenericSet extends tr.v.d.Diagnostic {
    /**
     * @param {!Iterable.<!PlainOldData>} values
     */
    constructor(values) {
      super();

      if (typeof values[Symbol.iterator] !== 'function') {
        throw new Error('GenericSet must be constructed from an interable.');
      }

      this.values_ = new Set(values);
      this.has_objects_ = false;

      for (const value of values) {
        if (typeof value === 'object') {
          this.has_objects_ = true;
        }
      }
    }

    get size() {
      return this.values_.size;
    }

    get length() {
      return this.values_.size;
    }

    * [Symbol.iterator]() {
      for (const value of this.values_) {
        yield value;
      }
    }

    has(value) {
      if (typeof value !== 'object') return this.values_.has(value);
      const json = JSON.stringify(value);
      for (const x of this) {
        if (typeof x !== 'object') continue;
        if (json === JSON.stringify(x)) return true;
      }
      return false;
    }

    equals(other) {
      if (!(other instanceof GenericSet)) return false;
      if (this.size !== other.size) return false;
      for (const value of this) {
        if (!other.has(value)) return false;
      }
      return true;
    }

    get hashKey() {
      if (this.has_objects_) return undefined;

      if (this.hash_key_ !== undefined) {
        return this.hash_key_;
      }

      let key = '';
      for (const value of Array.from(this.values_.values()).sort()) {
        key += value;
      }
      this.hash_key_ = key;
      return key;
    }

    serialize(serializer) {
      const i = [...this].map(x => serializer.getOrAllocateId(x));
      return (i.length === 1) ? i[0] : i;
    }

    asDictInto_(d) {
      d.values = Array.from(this);
    }

    static deserialize(data, deserializer) {
      if (!(data instanceof Array)) {
        data = [data];
      }
      return new GenericSet(data.map(datum => deserializer.getObject(datum)));
    }

    static fromDict(d) {
      return new GenericSet(d.values);
    }

    clone() {
      return new GenericSet(this.values_);
    }

    canAddDiagnostic(otherDiagnostic) {
      return otherDiagnostic instanceof GenericSet;
    }

    addDiagnostic(otherDiagnostic) {
      const jsons = new Set();
      for (const value of this) {
        if (typeof value !== 'object') continue;
        jsons.add(stableStringify(value));
      }

      for (const value of otherDiagnostic) {
        if (typeof value === 'object') {
          if (jsons.has(stableStringify(value))) {
            continue;
          }
          this.has_objects_ = true;
        }
        this.values_.add(value);
      }
    }
  }

  tr.v.d.Diagnostic.register(GenericSet, {
    elementName: 'tr-v-ui-generic-set-span'
  });

  return {
    GenericSet,
  };
});
</script>
